<?php

namespace Service\Model;

use mysqli_result;
use Service\Http\Exception\ModelException;
use Service\Http\Exception\RestException;
use Service\Util\Database;

abstract class Model
{
    use Database;

    protected $data = [];
    /**
     * @var string 表名
     */
    protected $table = '';

    /**
     * @var string 主键
     */
    protected $primaryKey = 'id';

    /**
     * @var array 字段名称
     */
    protected $fields = [];

    /**
     * @throws RestException
     */
    public function selectById($id)
    {
        $field_name = implode(',', $this->fields);
        $sql = "SELECT $field_name FROM $this->table WHERE $this->primaryKey = " . $this->safe_sql_value($id);
        $result = $this->executeSql($sql);
        if ($result->num_rows === 0) {
            throw new ModelException("$this->table 表的 $id 数据不存在");
        }
        $this->fillModelFields($result);
    }

    /**
     * @throws RestException
     */
    public function select(array $where, bool $first = true)
    {
        $field_name = implode(',', $this->fields);
        $sql = "SELECT $field_name FROM $this->table WHERE " . $this->makeWhereParam($where);
        $result = $this->executeSql($sql);
        if ($first) {
            $this->fillModelFields($result);
        }
        return $result;
    }

    /**
     * @throws RestException
     */
    public function selectPageable(array $where, $offset, $page_size, bool $first = true)
    {
        $field_name = implode(',', $this->fields);
        $sql = "SELECT $field_name FROM $this->table WHERE " . $this->makeWhereParam($where) . "limit $offset, $page_size";
        $result = $this->executeSql($sql);
        if ($first) {
            $this->fillModelFields($result);
        }
        return $result;
    }

    public function selectPageableCount(array $where)
    {
        $sql = "SELECT COUNT($this->primaryKey) FROM $this->table WHERE " . $this->makeWhereParam($where);
        $result = $this->executeSql($sql);
        return $result->fetch_all()[0];
    }

    /**
     * @throws RestException
     */
    public function update(array $where, array $mixed)
    {
        $update_values = [];
        foreach ($mixed as $key => $value) {
            array_push($update_values, "$key = " . $this->safe_sql_value($value));
        }
        $values = implode(',', $update_values);
        $sql = "UPDATE $this->table SET $values WHERE " . $this->makeWhereParam($where);
        return $this->executeSql($sql);
    }

    /**
     * @throws RestException
     */
    public function insert()
    {
        $values = [];
        foreach ($this->fields as $field) {
            array_push($values, $this->safe_sql_value($this->data[$field]));
        }
        $field_name = implode(',', $this->fields);
        $field_values = implode(',', $values);

        $sql = "INSERT INTO $this->table ($field_name) VALUES ($field_values)";
        return $this->executeSql($sql);
    }

    /**
     * @throws RestException
     */
    public function delete()
    {
        $sql = "DELETE FROM $this->table WHERE $this->primaryKey = " . $this->safe_sql_value($this->__get($this->primaryKey));
        return $this->executeSql($sql);
    }

    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }

    public function __get($name)
    {
        return $this->data[$name] ?? null;
    }

    private function fillModelFields(mysqli_result $result)
    {
        $result_array = $result->fetch_array(MYSQLI_ASSOC);
        foreach ($this->fields as $field) {
            $this->__set($field, $result_array[$field] ?? '');
        }
    }

    private function makeWhereParam(array $where): string
    {
        $where_param = [];
        foreach ($where as $item) {
            $where_field_name = $item[0];
            $where_operation = $item[1];
            $where_value = $item[2];
            array_push($where_param, "$where_field_name $where_operation " . $this->safe_sql_value($where_value));
        }
        return implode(' AND ', $where_param);
    }

    private function safe_sql_value($value): string
    {
        if (gettype($value) === 'string') {
            return '"' . addslashes($value) . '"';
        } else {
            return addslashes($value);
        }
    }

    public function to_json()
    {
        $result = [];
        foreach ($this->fields as $field) {
            $result[$field] = $this->__get($field);
        }
        return json_encode($result);
    }

}